import { dialog, ipcMain } from 'electron'
import { promises as fs, watch, FSWatcher } from 'fs'
import path from 'path'
import { FileInfosType } from '../renderer/src/types'
import { debounce } from 'radash'

/**
 * params: dir - 文件夹路径
 * return: [{ name: 文件名, path: 文件完整路径 }]
 * */
async function getMDListByDir(dir: string): Promise<FileInfosType> {
  /**
   * readdir参数说明
   * withFileTypes: boolean - 如果为 true，则返回 fs.Dirent 对象数组，否则返回字符串数组
      Dirent 对象可用于判断文件类型 Dirent { name: 'readme.md', isFile: [Function: isFile] }例如：
        e.isFile()        // 是否是文件
        e.isDirectory()   // 是否是目录
        e.isSymbolicLink()// 是否是符号链接
  */
  const entries = await fs.readdir(dir, { withFileTypes: true })
  const files = entries.reduce<FileInfosType>((acc, entry) => {
    if (entry.isFile()) {
      const lastIndex = entry.name.lastIndexOf('.')
      acc.push({
        fileFullName: entry.name,
        path: path.join(dir, entry.name),
        fileName: entry.name.substring(0, lastIndex),
        fileType: entry.name.substring(lastIndex)
      })
    }
    return acc
  }, [])
  return files
}

export default function registerFsIpc(): void {
  ipcMain.handle('fs-open-folder-dialog', async () => {
    /**
     * 打开选择文件夹对话框 {properties: ['openDirectory']} 表示只能选择文件夹
     * 参数说明: https://www.electronjs.org/zh/docs/latest/api/dialog#dialogshowopendialogbrowserwindow-options
     * 返回结果说明:
     *  canceled: boolean,       // 用户是否取消选择
        filePaths: string[],     // 用户选择的路径数组
        bookmarks?: string[]     // macOS 上才有，用于保存安全访问书签（sandbox）
    */
    const result = await dialog.showOpenDialog({
      title: '请选择笔记目录',
      properties: ['openDirectory']
    })
    if (result.canceled || !result.filePaths?.length) {
      return { folderPath: '', files: [] as FileInfosType }
    }
    const folderPath = result.filePaths[0]
    const files = await getMDListByDir(folderPath)
    return { folderPath, files }
  })

  // _ 表示事件对象，这里不需要用到所以用 _ 代替（https://www.electronjs.org/zh/docs/latest/api/structures/ipc-main-invoke-event）
  ipcMain.handle('fs-list-md', async (_, folderPath: string) => {
    if (!folderPath) return []
    return getMDListByDir(folderPath)
  })

  ipcMain.handle('fs-read-file', async (_, filePath: string) => {
    if (!filePath) return ''
    const buf = await fs.readFile(filePath)
    return buf.toString('utf-8')
  })

  // 记录各渲染进程最近一次主动写入的文件时间戳，用于抑制自写触发的文件变更事件
  const selfWrites = new Map<number, Map<string, number>>()

  ipcMain.handle('fs-write-file', async (event, filePath: string, content: string) => {
    if (!filePath) return false
    await fs.writeFile(filePath, content ?? '', 'utf-8')
    const senderId = event.sender.id
    if (!selfWrites.has(senderId)) selfWrites.set(senderId, new Map())
    selfWrites.get(senderId)!.set(filePath, Date.now())
    return true
  })

  ipcMain.handle('fs-rename-file', async (_, filePath: string, newName: string) => {
    if (!filePath || !newName) return ''
    // path.dirname(filePath) 获取文件所在文件夹路径
    const folder = path.dirname(filePath)
    // path.join 拼接新的文件完整路径
    const target = path.join(folder, newName)
    await fs.rename(filePath, target)
    return target
  })

  ipcMain.handle('fs-delete-file', async (_, filePath: string) => {
    if (!filePath) return false
    await fs.unlink(filePath)
    return true
  })

  // ===== 文件系统监听 =====
  const folderWatchers = new Map<number, FSWatcher>()
  const fileWatchers = new Map<number, FSWatcher>()

  // 防抖函数清除
  let debounceFolder: ReturnType<typeof debounce> | null = null
  let debounceFile: ReturnType<typeof debounce> | null = null

  const stopWatchFor = (senderId: number, type: 'folder' | 'file' | 'all' = 'all') => {
    // 关闭监听
    if (type !== 'file') {
      debounceFolder?.cancel?.()
      folderWatchers.get(senderId)?.close()
      // 清除Map记录
      folderWatchers.delete(senderId)
    }
    if (type !== 'folder') {
      debounceFile?.cancel?.()
      fileWatchers.get(senderId)?.close()
      fileWatchers.delete(senderId)
    }
  }

  ipcMain.handle('fs-watch', (event, options: { folderPath?: string; filePath?: string }) => {
    console.log(event.sender.id, 'id')
    const senderId = event.sender.id
    const { folderPath, filePath } = options

    if (folderPath) {
      console.log('watching folderPath:', folderPath)
      stopWatchFor(senderId, 'folder')
      // watch: 监听文件或文件夹变化，返回 FSWatcher 对象（文件夹路径，{} 监听选项 recursive: true 递归监听子文件夹变化）
      // 参数详细说明： https://nodejs.org/api/fs.html#fswatchfilename-options-listener
      debounceFolder?.cancel?.()
      debounceFolder = debounce({ delay: 1000 }, (eventType, filename) => {
        console.log('变化的文件：', eventType, filename)

        // 只监控rename 排除临时文件变化
        if (eventType === 'rename' && filename.startsWith('.~')) return
        console.log(eventType, filename, 'folderChanged')
        event.sender.send('fs-files-changed') // 通知渲染进程
      })
      const fw = watch(folderPath, debounceFolder)
      folderWatchers.set(senderId, fw)
    }
    // 监听特定文件变化
    if (filePath) {
      console.log('watching file:', filePath)
      stopWatchFor(senderId, 'file')

      debounceFile?.cancel?.()
      debounceFile = debounce({ delay: 1000 }, (eventType, filename) => {
        // 抑制自身写入导致的刷新（阈值 1500ms）
        const ts = selfWrites.get(senderId)?.get(filePath)
        if (ts && Date.now() - ts < 1500) {
          // 清除标记，避免长期占用
          selfWrites.get(senderId)?.delete(filePath)
          return
        }
        console.log(eventType, filename, 'fileChanged')
        event.sender.send('fs-file-changed', filePath)
      })

      fileWatchers.set(senderId, watch(filePath, debounceFile))
    }
    return true
  })

  ipcMain.handle('fs-watch-stop', (event) => {
    const senderId = event.sender.id
    stopWatchFor(senderId)
    return true
  })
}
